/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

#include "common.h"
#include "Graphics.h"
#include "Util.h"


namespace Scenic 
{ 
namespace Renderer
{

class DXIndexBuffer : public IndexBuffer
{
	IDirect3DIndexBuffer9 *buffer;
	int size;
public:
	DXIndexBuffer(Device *device, int size);
	~DXIndexBuffer();

	void *lock(int offset, int size);
	void unlock();
	int getSize() { return size; }

	IDirect3DIndexBuffer9 *getBuffer() { return buffer; }

	friend class Device;
};

class DXVertexBuffer : public VertexBuffer
{
	IDirect3DVertexBuffer9 *buffer;
	int size;
public:
	DXVertexBuffer(Device *device, int size);
	~DXVertexBuffer();

	void *lock(int offset, int size);
	void unlock();
	int getSize() { return size; }

	IDirect3DVertexBuffer9 *getBuffer() { return buffer; }

	friend class Device;
};

}}

using namespace Scenic::Renderer;


FormatInfo formats[FMT_COUNT] = 
{
	{FMT_UNKNOWN, D3DFMT_A8R8G8B8, 4},
	{FMT_A8R8G8B8, D3DFMT_R8G8B8, 3},
	{FMT_A8R8G8B8, D3DFMT_L8, 1},
	{FMT_A8R8G8B8, D3DFMT_A8, 1},
	{FMT_A8R8G8B8, D3DFMT_UNKNOWN, 1},
	// 5
	{FMT_A8R8G8B8, D3DFMT_UNKNOWN, 4},
	{FMT_UNKNOWN, D3DFMT_A16B16G16R16F, 8} ,
	{FMT_ABGR_16F, D3DFMT_A32B32G32R32F, 16},
	{FMT_ABGR_32F, D3DFMT_UNKNOWN, 4},
	{FMT_ABGR_32F, D3DFMT_UNKNOWN, 8},
	// 10
	{FMT_A8R8G8B8, D3DFMT_A8B8G8R8, 4},
	{FMT_R8G8B8, D3DFMT_X8R8G8B8, 4},
	{FMT_X8R8G8B8, D3DFMT_X8B8G8R8, 4},
	{FMT_X8R8G8B8, D3DFMT_UNKNOWN, 4},
	{FMT_X8R8G8B8, D3DFMT_UNKNOWN, 4},
	// 15
	{FMT_A8R8G8B8, D3DFMT_UNKNOWN, 4},
	{FMT_A8R8G8B8, D3DFMT_UNKNOWN, 4}
};

//
// Device
//

IDirect3DDevice9 *direct3DDevice = 0;
int direct3DDeviceRefCount = 0;
Capabilities deviceCaps;

static IDirect3D9 *d3d = 0;
static ResourcePool devicePool("Device");
static int beginSceneCount = 0;

Capabilities &Device::getCaps()
{
	if(!d3d)
	{
		deviceCaps.maxTextureWidth = 2048;
		deviceCaps.maxTextureHeight = 2048;
	}
	return deviceCaps;
}

static bool checkFormat(D3DFORMAT format, DWORD usage)
{
	return d3d->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8, usage, D3DRTYPE_TEXTURE, format) == D3D_OK;
}

static bool initDirect3D()
{
	printf("Creating Direct3D...\n");
	d3d = Direct3DCreate9(D3D_SDK_VERSION);
	if(d3d == NULL)
	{
		printf("Failed\n");
		return false;
	}
	printf("OK\n");
	return true;
}

static void initPresentParameters(D3DPRESENT_PARAMETERS *d3dpp, HWND hwnd)
{
	ZeroMemory(d3dpp, sizeof(D3DPRESENT_PARAMETERS));
	d3dpp[0].Windowed = TRUE;
	d3dpp[0].SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp[0].BackBufferFormat = D3DFMT_UNKNOWN;
	d3dpp[0].hDeviceWindow = hwnd;
	d3dpp[0].EnableAutoDepthStencil = FALSE;
//	d3dpp[0].AutoDepthStencilFormat = D3DFMT_D16;
	d3dpp[0].PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
}


void createDirect3DDevice(D3DPRESENT_PARAMETERS *d3dpp, HWND hwnd)
{
	printf("Creating Direct3DDevice...\n");

	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
                                D3DCREATE_MULTITHREADED | D3DCREATE_HARDWARE_VERTEXPROCESSING,
                                d3dpp, &direct3DDevice)))
	{
		if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
									D3DCREATE_MULTITHREADED | D3DCREATE_SOFTWARE_VERTEXPROCESSING,
									d3dpp, &direct3DDevice)))
		{
			printf("Failed\n");
			return;
		}
		printf("OK\nUsing software vertex processing\n");
	}
	else
		printf("OK\nUsing hardware vertex processing\n");

	D3DCAPS9 d3dcaps;

	memset(&deviceCaps, 0, sizeof(deviceCaps));
	d3d->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dcaps);
	deviceCaps.d3dcaps = d3dcaps;
	deviceCaps.nonPow2Conditional = (d3dcaps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL) ? true : false;
	for(int i = 0; i < FMT_COUNT; i++)
	{
		if(formats[i].d3dFormat != D3DFMT_UNKNOWN)
		{
			deviceCaps.textureFormats[i] = checkFormat(formats[i].d3dFormat, 0);
			deviceCaps.autogenFormats[i] = checkFormat(formats[i].d3dFormat, D3DUSAGE_AUTOGENMIPMAP);
		}
		else
		{
			deviceCaps.textureFormats[i] = false;
			deviceCaps.autogenFormats[i] = false;
		}
		printf("Format supported: %d %s\n", i, deviceCaps.textureFormats[i] ? "true" : "false");
	}
/*	// Check available texture formats
	deviceCaps.textureFormats[FMT_A8R8G8B8] = true;
	deviceCaps.textureFormats[FMT_R8G8B8] = checkFormat(D3DFMT_R8G8B8, 0);
	deviceCaps.textureFormats[FMT_L8] = checkFormat(D3DFMT_L8, 0);
	deviceCaps.textureFormats[FMT_A8] = checkFormat(D3DFMT_A8, 0);
	deviceCaps.textureFormats[FMT_AL8] = false;
	deviceCaps.textureFormats[FMT_A16B16G16R16F] = checkFormat(D3DFMT_A16B16G16R16F, 0);
	// Check texture formats that can use automatically generated mipmaps
	deviceCaps.autogenFormats[FMT_A8R8G8B8] = checkFormat(D3DFMT_A8R8G8B8, D3DUSAGE_AUTOGENMIPMAP);
	deviceCaps.autogenFormats[FMT_R8G8B8] = checkFormat(D3DFMT_R8G8B8, D3DUSAGE_AUTOGENMIPMAP);
	deviceCaps.autogenFormats[FMT_L8] = checkFormat(D3DFMT_L8, D3DUSAGE_AUTOGENMIPMAP);
	deviceCaps.autogenFormats[FMT_A8] = checkFormat(D3DFMT_A8, D3DUSAGE_AUTOGENMIPMAP);
	deviceCaps.autogenFormats[FMT_AL8] = false;
	deviceCaps.autogenFormats[FMT_A16B16G16R16F] = checkFormat(D3DFMT_A16B16G16R16F, D3DUSAGE_AUTOGENMIPMAP); */

	deviceCaps.maxTextureWidth = d3dcaps.MaxTextureWidth;
	deviceCaps.maxTextureHeight = d3dcaps.MaxTextureHeight;

	printf("\n---DEVICE CAPABILITIES---\n");
	printf("Conditional non power of 2 textures: %s\n", 
		deviceCaps.nonPow2Conditional ? "yes" : "no");
	printf("R8G8B8 texture format: %s\n", deviceCaps.textureFormats[FMT_R8G8B8] ? "yes" : "no");
	printf("L8 texture format: %s\n", deviceCaps.textureFormats[FMT_L8] ? "yes" : "no");
	printf("A8 texture format: %s\n", deviceCaps.textureFormats[FMT_A8] ? "yes" : "no");
}

Device::Device(HWND hwnd)
:Resource(&devicePool)
{
	device = 0;
	swapChain = 0;
	this->hwnd = hwnd;

	init();
}

void Device::init()
{
	D3DPRESENT_PARAMETERS d3dpp[1]; 

	if(!d3d)
	{
		if(!initDirect3D())
			return;
	}

	initPresentParameters(d3dpp, hwnd);

	if(!direct3DDevice)
	{
		createDirect3DDevice(d3dpp, hwnd);
		device = direct3DDevice;
		device->GetSwapChain(D3DADAPTER_DEFAULT, &swapChain);
		implicitSwapChain = true;
		Texture::loadAll();
	}
	else if(!device)
	{
		device = direct3DDevice;
		device->AddRef();
	}

	if(!swapChain)
	{
		device->CreateAdditionalSwapChain(d3dpp, &swapChain);
		implicitSwapChain = false;
	}

	caps = deviceCaps;
    device->SetRenderState(D3DRS_LIGHTING, FALSE);
	device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	setState(STATE_BLENDENABLE, BOOLEAN_TRUE);
}

Device::~Device()
{
	swapChain->Release();
	if(implicitSwapChain)
	{
		DynArray<Resource*> &devices = devicePool.getAll();
		int i;

		for(i = 0; i < devices.size; i++)
		{
			Device *d = (Device*)devices[i];

			if(d && d != this && d->swapChain)
			{
				d->swapChain->Release();
				d->swapChain = 0;
				d->device->Release();
				d->device = 0;
			}
		}
		Texture::freeAll();

		if(device->Release() != 0)
			fprintf(stderr, "Direct3DDevice reference count > 0!\n");

		direct3DDevice = 0;

		for(i = 0; i < devices.size; i++)
		{
			Device *d = (Device*)devices[i];

			if(d && d != this)
			{
				d->init();
			}
		}
	}
	else
	{
		device->Release();
	}
}

Device *Device::getDefaultDevice()
{
	DynArray<Resource*> &devices = devicePool.getAll();

	if(devices.size > 0)
		return (Device*)devices[0];
	return NULL;
}

int Device::getFormatSize(Format format)
{
	return formats[format].size;
}

void Device::reset()
{
	D3DPRESENT_PARAMETERS d3dpp[1]; 
		
	initPresentParameters(d3dpp, hwnd);

	if(implicitSwapChain)
	{
		DynArray<Resource*> &devices = devicePool.getAll();
		int i;

		for(i = 0; i < devices.size; i++)
		{
			Device *d = (Device*)devices[i];

			if(d && !d->implicitSwapChain && d->swapChain)
			{
				d->swapChain->Release();
				d->swapChain = 0;
			}
		}

		swapChain->Release();

		Texture::resetAll();

		HRESULT result = device->Reset(d3dpp);
		
		if(result != D3D_OK)
			printf("Reset failed (%s)\n", getErrorName(result));

		device->GetSwapChain(D3DADAPTER_DEFAULT, &swapChain);

		device->SetRenderState(D3DRS_LIGHTING, FALSE);
		device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
		setState(STATE_BLENDENABLE, BOOLEAN_TRUE); 

		for(i = 0; i < devices.size; i++)
		{
			Device *d = (Device*)devices[i];

			if(d && !d->implicitSwapChain)
				d->init();
		}
	}
	else
	{
		if(swapChain)
			swapChain->Release();
		device->CreateAdditionalSwapChain(d3dpp, &swapChain);
	}
}

void Device::initializeViewport(int width, int height)
{
	D3DMATRIX pm;
	D3DVIEWPORT9 vp;

	ZeroMemory(&pm, sizeof(pm));

	pm.m[0][0] = 2.0f / width;
	pm.m[1][1] = -2.0f / height;
	pm.m[3][0] = -1.0f - (2.0f / width) * 0.5f;
	pm.m[3][1] = 1.0f + (2.0f / height) * 0.5f;
	pm.m[2][2] = 1.0f;
	pm.m[3][3] = 1.0f;

	device->SetTransform(D3DTS_PROJECTION, &pm);

	vp.X = 0;
	vp.Y = 0;
	vp.Width = width;
	vp.Height = height;
	vp.MinZ = 0.0f;
	vp.MaxZ = 1.0f;

	device->SetViewport(&vp);
}

bool Device::beginScene()
{
	IDirect3DSurface9 *backBuffer;

	swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backBuffer);
	device->SetRenderTarget(0, backBuffer);
	backBuffer->Release();

	if(beginSceneCount == 0)
	{
		bool result = device->BeginScene() == D3D_OK;

		if(result)
			beginSceneCount++;
		return result;
	}
	else
		beginSceneCount++;

	return true;
}

void Device::endScene()
{
	beginSceneCount--;
	if(beginSceneCount == 0)
		device->EndScene();
}

void Device::present()
{
	swapChain->Present(NULL, NULL, NULL, NULL, 0);
}

void Device::clear(const RGBAFloat &color, SizeRect *rect)
{
	if(rect)
	{
		D3DRECT r;

		r.x1 = rect->x;
		r.y1 = rect->y;
		r.x2 = rect->x + rect->width;
		r.y2 = rect->y + rect->height;
		device->Clear(1, &r, D3DCLEAR_TARGET, color.getRGBA32(), 0.0f, 0);
	}
	else
	{
		device->Clear(0, NULL, D3DCLEAR_TARGET, color.getRGBA32(), 0.0f, 0);
	}
}

void Device::setRenderTarget(Surface *surface)
{
	device->SetRenderTarget(0, surface->surface);
}

void Device::setRenderTarget(Texture *texture)
{
	IDirect3DSurface9 *surface;

	texture->texture->GetSurfaceLevel(0, &surface);
	device->SetRenderTarget(0, surface);
	surface->Release();
}

Surface *Device::getRenderTarget()
{
	IDirect3DSurface9 *surface = NULL;

	device->GetRenderTarget(0, &surface);

	if(!surface)
		return NULL;

	return new Surface(0, surface);
}

void Device::setTexture(int stage, BaseTexture *texture)
{
	if(texture)
	{
		device->SetTexture(stage, texture->getTexture());
	}
	else
	{
		device->SetTexture(stage, 0);
		device->SetTextureStageState(stage, D3DTSS_COLOROP, D3DTOP_DISABLE);
		device->SetTextureStageState(stage, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
	}
}

void Device::setSamplerState(int stage, SamplerStateType type, int value)
{
	device->SetSamplerState(stage, (_D3DSAMPLERSTATETYPE)type, value);
}

void Device::setSamplerStates(int stage, Filter filter, Address address)
{
	setSamplerState(stage, SAMPLER_ADDRESSU, address);
	setSamplerState(stage, SAMPLER_ADDRESSV, address);
	setSamplerState(stage, SAMPLER_ADDRESSW, address);
	setSamplerState(stage, SAMPLER_MAG_FILTER, filter);
	setSamplerState(stage, SAMPLER_MIN_FILTER, filter);
	setSamplerState(stage, SAMPLER_MIP_FILTER, FILTER_POINT);
}

void Device::setTextureStageModulation(int stage, int modulation)
{
	int m;

	if(modulation == 1)
		m = D3DTOP_MODULATE;
	else if(modulation == 2)
		m = D3DTOP_MODULATE2X;
	else if(modulation == 4)
		m = D3DTOP_MODULATE4X;

	device->SetTextureStageState(stage, D3DTSS_COLOROP, m);
	device->SetTextureStageState(stage, D3DTSS_ALPHAOP, m);
}

void Device::setState(State state, int value)
{
	device->SetRenderState((_D3DRENDERSTATETYPE)state, value);
}

void Device::setBlendFunc(BlendFactor sfactor, BlendFactor dfactor)
{
	device->SetRenderState(D3DRS_SRCBLEND, sfactor);
	device->SetRenderState(D3DRS_DESTBLEND, dfactor);
}

void Device::setClipRect(float left, float top, float right, float bottom)
{
	float planes[4][4] = {
		{1, 0, 0, -left},
		{0, 1, 0, -top},
		{-1, 0, 0, right},
		{0, -1, 0, bottom}};

	device->SetRenderState(D3DRS_CLIPPLANEENABLE, 0xf);
	device->SetClipPlane(0, planes[0]);
	device->SetClipPlane(1, planes[1]);
	device->SetClipPlane(2, planes[2]);
	device->SetClipPlane(3, planes[3]);
}


int Device::getVisualId(int screen)
{
	return -1;
}

static int convertAttribute(Attribute a)
{
	if(a == POSITION_ATTRIBUTE)
		return D3DDECLUSAGE_POSITION;
	if(a == COLOR_ATTRIBUTE)
		return D3DDECLUSAGE_COLOR;
	if(a == TEXCOORD_ATTRIBUTE)
		return D3DDECLUSAGE_TEXCOORD;
	return 0;
}

static D3DVERTEXELEMENT9 convertElement(ElementInfo &e, int &offset)
{
	D3DVERTEXELEMENT9 r;

	r.Stream = 0;
	if(e.offset != -1)
		r.Offset = e.offset;
	else
		r.Offset = offset;
	if(e.type == ATTRIBUTE_TYPE_FLOAT && e.size >= 1 && e.size <= 4)
	{
		r.Type = D3DDECLTYPE_FLOAT1 + e.size - 1;
	}
	else if(e.type == ATTRIBUTE_TYPE_CHAR && e.size == 4)
	{
		r.Type = D3DDECLTYPE_D3DCOLOR;
	}
	r.Method = D3DDECLMETHOD_DEFAULT;
	r.Usage = convertAttribute(e.usage);
	r.UsageIndex = e.usageIndex;

	offset += e.getSize();

	return r;
}

int ElementInfo::getSize()
{
	int s;

	if(type == ATTRIBUTE_TYPE_FLOAT)
		s = 4;
	else if(type == ATTRIBUTE_TYPE_INT)
		s = 4;
	else if(type == ATTRIBUTE_TYPE_SHORT)
		s = 2;
	else if(type == ATTRIBUTE_TYPE_CHAR)
		s = 1;

	return s * size;
}

void Device::draw(VertexDeclaration *decl, VertexBuffer *vertices, IndexBuffer *indices, int start, int numPrimitives)
{
	HRESULT res;
	int size;

	if(!decl->handle)
	{
		D3DVERTEXELEMENT9 *em = new D3DVERTEXELEMENT9[decl->numElements + 1];
		D3DVERTEXELEMENT9 end = D3DDECL_END();
		int i;
		int offset = 0;

		for(i = 0; i < decl->numElements; i++)
		{
			em[i] = convertElement(decl->elements[i], offset);
		}
		em[decl->numElements] = end;

		res = device->CreateVertexDeclaration(em, &decl->handle);
		delete em;
		if(res != D3D_OK)
		{
			fprintf(stderr, "IDirect3DDevice9::CreateVertexDeclaration failed! (result=%s)\n", getErrorName(res));
			return;
		}
	}
	size = decl->getSize();
	res = device->SetVertexDeclaration(decl->handle);
	if(res != D3D_OK)
	{
		fprintf(stderr, "IDirect3DDevice9::SetVertexDeclaration failed! (result=%s)\n", getErrorName(res));
		return;
	}
	res = device->SetStreamSource(0, ((DXVertexBuffer*)vertices)->buffer, 0, size);
	if(res != D3D_OK)
	{
		fprintf(stderr, "IDirect3DDevice9::SetStreamSource failed! (result=%s)\n", getErrorName(res));
		return;
	}
	res = device->SetIndices(((DXIndexBuffer*)indices)->buffer);
	if(res != D3D_OK)
	{
		fprintf(stderr, "IDirect3DDevice9::SetIndices failed! (result=%s)\n", getErrorName(res));
		return;
	}
	res = device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 
		vertices->getSize() / size, start, numPrimitives);
	if(res != D3D_OK)
	{
		fprintf(stderr, "IDirect3DDevice9::DrawIndexedPrimitive failed! (result=%s)\n", getErrorName(res));
		return;
	}
	device->SetStreamSource(0, NULL, 0, 0);
	device->SetIndices(NULL);
	device->SetVertexDeclaration(NULL);
}

IndexBuffer *Device::createIndexBuffer(int size)
{
	return new DXIndexBuffer(this, size);
}

VertexBuffer *Device::createVertexBuffer(int size)
{
	return new DXVertexBuffer(this, size);
}

//
// Surface
//

Surface::Surface(Texture *texture, IDirect3DSurface9 *surface)
{
	refCount = 1;
	this->texture = texture;
	this->surface = surface;
	if(texture)
	{
		format = texture->format;
		surfaceFormat = texture->textureFormat;
	}
}

Surface::~Surface()
{
	if(refCount > 0)
		printf("Surface reference count > 0\n");
	surface->Release();
}

void Surface::addRef()
{
	refCount++;
}

void Surface::release()
{
	refCount--;
	if(refCount == 0)
		delete this;
}

SurfaceInfo Surface::getInfo()
{
	D3DSURFACE_DESC desc;
	SurfaceInfo info;

	surface->GetDesc(&desc);
	info.width = desc.Width;
	info.height = desc.Height;

	return info;
}

//
// Texture
//


ResourcePool texturePool("Texture");

BaseTexture::BaseTexture()
	: Resource(&texturePool)
{
}

Texture::Texture(int width, int height, Format format, int usage, int levels)
{
	this->texture = 0;
	this->width = width;
	this->height = height;
	this->format = format;
	this->usage = usage;
	this->levels = levels;
	localData = 0;

	if(direct3DDevice)
		createTexture();
//	else
//		localData = new char[width * height * formats[textureFormat].size];
}

static int usedTextureMemory = 0;

Texture::~Texture()
{
	if(texture)
	{
		texture->Release();
		usedTextureMemory -= width * height * formats[textureFormat].size;
//		printf("Used memory: %d\n", usedTextureMemory);
	}
	if(localData)
		delete localData;
}

IDirect3DBaseTexture9 *Texture::getTexture()
{
	load();
	return texture;
}

void Texture::createTexture()
{
	if(!direct3DDevice)
		return;
	textureFormat = format;
	while(textureFormat != FMT_UNKNOWN && !deviceCaps.textureFormats[textureFormat])
		textureFormat = formats[textureFormat].backupFormat;

#if 0
	printf("Texture::createTexture (width = %d, height = %d, format = %d, textureFormat = %d, levels = %d, usage = %d)\n", 
		width, height, format, textureFormat, levels, usage);
#endif

	HRESULT res;

	if(usage & USAGE_RENDER_TARGET)
	{
		autogenMipmaps = false;
		res = direct3DDevice->CreateTexture(width, height, 1, D3DUSAGE_RENDERTARGET, 
			formats[textureFormat].d3dFormat, D3DPOOL_DEFAULT, &texture, NULL);
	}
	else 
	{
		autogenMipmaps = levels != 1 && deviceCaps.autogenFormats[textureFormat];
		autogenMipmaps = false;
		res = direct3DDevice->CreateTexture(width, height, levels, autogenMipmaps ? D3DUSAGE_AUTOGENMIPMAP : 0, 
			formats[textureFormat].d3dFormat, D3DPOOL_MANAGED, &texture, NULL);
	}

	if(res != D3D_OK) 
	{
		printf("CreateTexture failed (result=%s)\n", getErrorName(res));
		printf("   (width=%d, height=%d, textureFormat=%d)\n", width, height, textureFormat);
		return;
	}
	usedTextureMemory += width * height * formats[textureFormat].size;
}

bool Texture::contentsLost()
{
	if(!texture && !localData)
		return true;
	return false;
}

void Texture::freeAll()
{
	DynArray<Resource*> &textures = texturePool.getAll();

	for(int i = 0; i < textures.size; i++)
		if(textures[i])
			((BaseTexture*)textures[i])->free();
}

void Texture::resetAll()
{
	DynArray<Resource*> &textures = texturePool.getAll();

	for(int i = 0; i < textures.size; i++)
		if(textures[i])
			((BaseTexture*)textures[i])->reset();
}

void Texture::loadAll()
{
	DynArray<Resource*> &textures = texturePool.getAll();

	for(int i = 0; i < textures.size; i++)
		if(textures[i])
			((BaseTexture*)textures[i])->load();
}

void Texture::reset()
{
	if(usage & USAGE_RENDER_TARGET)
	{
		if(texture)
		{
			if(texture->Release() != 0)
				fprintf(stderr, "Texture reference count > 0!\n");
			texture = 0;
		}
	}	
}

void Texture::free()
{
	if(!texture)
	{
		return;
	}
	if(!(usage & USAGE_RENDER_TARGET))
	{
		D3DLOCKED_RECT lockedRect;
		
		texture->LockRect(0, &lockedRect, NULL, 0);
		
		int nc = formats[textureFormat].size;
		char *src = (char*)lockedRect.pBits;
		
		localData = new char[width * height * nc];
		
		for(int y = 0; y < height; y++)
			memcpy(localData + y * width * nc, src + y * lockedRect.Pitch, width * nc);
		
		texture->UnlockRect(0);
	}
	if(texture->Release() != 0)
		fprintf(stderr, "Texture reference count > 0!\n");
	texture = 0;
}

void Texture::load()
{
	if(texture)
		return;
	if(usage & USAGE_RENDER_TARGET)
		return;

	D3DLOCKED_RECT lockedRect;

	createTexture();
	if(!texture)
		return;
	texture->LockRect(0, &lockedRect, NULL, 0);

	int nc = formats[textureFormat].size;
	char *dst = (char*)lockedRect.pBits;

	for(int y = 0; y < height; y++)
		memcpy(dst + y * lockedRect.Pitch, localData + y * width * nc, width * nc);

	delete localData;
	localData = 0;
	texture->UnlockRect(0);
	if(levels != 1 && !autogenMipmaps)
		generateMipmaps();
}

Surface *Texture::getSurface(int level)
{
	IDirect3DSurface9 *surface;

	texture->GetSurfaceLevel(level, &surface);

	return new Surface(this, surface);
}

void Texture::write(int level, int x, int y, int width, int height, void *data, int pitch, Format dataFormat)
{
	if(width == 0 || height == 0)
	{
		return;
	}
	if(pitch == 0)
	{
		pitch = width * formats[dataFormat].size;
	}

	if(!texture)
		load();
	if(texture)
	{
		writeTexture(level, x, y, width, height, data, pitch, dataFormat);
		if(levels != 1 && !autogenMipmaps)
			generateMipmaps();
	}
	else
	{
		int size = formats[textureFormat].size;
		char *src = (char*)data;
		int srcPitch = pitch;
		char *dst = localData + (this->width * y + x) * size;
		int dstPitch = this->width * size;

		convert(src, srcPitch, dataFormat, 
			dst, dstPitch, textureFormat,
			width, height);
	}
}

void Texture::writeTexture(int level, int x, int y, int width, int height, void *data, int pitch, Format dataFormat)
{
	if(usage & USAGE_RENDER_TARGET)
	{
		writeRenderTargetTexture(level, x, y, width, height, data, pitch, dataFormat);
	}
	else
	{
		writePlainTexture(level, x, y, width, height, data, pitch, dataFormat);
	}
}

void Texture::writePlainTexture(int level, int x, int y, int width, int height, void *data, int pitch, Format dataFormat)
{
	D3DLOCKED_RECT lockedRect;
	RECT rect;

	rect.left = x;
	rect.top = y;
	rect.right = x + width;
	rect.bottom = y + height;

	texture->LockRect(level, &lockedRect, &rect, D3DLOCK_DISCARD);

	char *src = (char*)data;
	char *dst = (char*)lockedRect.pBits;

	convert(src, pitch, dataFormat, 
		dst, lockedRect.Pitch, textureFormat,
		width, height);

	texture->UnlockRect(level);
}

/*
 * This function writes data to a render target texture. On DirectX this
 * is done by first writing data to a tempory offscreen surface and
 * using UpdateSurface to copy the data to the render target texture.
 */
void Texture::writeRenderTargetTexture(int level, int x, int y, int width, int height, void *data, int pitch, Scenic::Renderer::Format format)
{
	D3DLOCKED_RECT lockedRect;
	POINT dest;

	dest.x = x;
	dest.y = y;

	IDirect3DSurface9 *surface;
	IDirect3DSurface9 *tempSurface;

	direct3DDevice->CreateOffscreenPlainSurface(width, height, formats[this->textureFormat].d3dFormat,
		D3DPOOL_SYSTEMMEM, &tempSurface, NULL);

	tempSurface->LockRect(&lockedRect, NULL, D3DLOCK_DISCARD);

	char *src = (char*)data;
	char *dst = (char*)lockedRect.pBits;

	convert(src, pitch, format,
		dst, lockedRect.Pitch, textureFormat,
		width, height);
	tempSurface->UnlockRect();

	texture->GetSurfaceLevel(0, &surface);
	direct3DDevice->UpdateSurface(tempSurface, NULL, surface, &dest);
	surface->Release();
	tempSurface->Release();
}

void Texture::read(int level, int x, int y, int width, int height, void *data, int pitch, Scenic::Renderer::Format format)
{
	if(level != 0)
	{
		fprintf(stderr, "Texture::read level != 0\n");
		return;
	}
	if(width == 0 || height == 0)
	{
		return;
	}
	if(pitch == 0)
	{
		pitch = width * formats[format].size;
	}

	if(texture)
	{
		readTexture(level, x, y, width, height, data, pitch, format);
	}
	else
	{
		int size = formats[textureFormat].size;
		char *dst = (char*)data;
		int dstPitch = pitch;
		char *src = localData + (this->width * y + x) * size;
		int srcPitch = this->width * size;

		convert(src, srcPitch, textureFormat,
			dst, dstPitch, format, 
			width, height);
	}
}

void Texture::readTexture(int level, int x, int y, int width, int height, void *data, int pitch, Scenic::Renderer::Format format)
{
	if(usage & USAGE_RENDER_TARGET)
	{
		readRenderTargetTexture(level, x, y, width, height, data, pitch, format);
	}
	else
	{
		readPlainTexture(level, x, y, width, height, data, pitch, format);
	}
}

void Texture::readPlainTexture(int level, int x, int y, int width, int height, void *data, int pitch, Scenic::Renderer::Format format)
{
	D3DLOCKED_RECT lockedRect;
	RECT rect;

	rect.left = x;
	rect.top = y;
	rect.right = x + width;
	rect.bottom = y + height;

	texture->LockRect(level, &lockedRect, &rect, D3DLOCK_READONLY);

	char *dst = (char*)data;
	char *src = (char*)lockedRect.pBits;

	convert(src, lockedRect.Pitch, textureFormat,
		dst, pitch, format,
		width, height);

	texture->UnlockRect(level);
}

void Texture::generateMipmaps()
{
	int numLevels = levels == 0 ? texture->GetLevelCount() : levels;
	int nc = formats[textureFormat].size;
	int w = max(1, width / 2), h = max(1, height / 2);
	int pw = width, ph = height;
	int xm, ym, shift;
	D3DLOCKED_RECT srcRect, dstRect;

	for(int level = 1; level < numLevels; level++)
	{
		xm = pw / w;
		ym = ph / h;
		shift = (xm - 1) + (ym - 1);

		texture->LockRect(level - 1, &srcRect, NULL, 0);
		texture->LockRect(level, &dstRect, NULL, D3DLOCK_DISCARD);

		for(int y = 0; y < h; y++)
		{
			char *src = (char*)srcRect.pBits + y * ym * srcRect.Pitch;
			char *dst = (char*)dstRect.pBits + y * dstRect.Pitch;

			for(int x = 0; x < w; x++)
			{
				for(int c = 0; c < nc; c++)
				{
					int sum = 0;

					for(int xx = 0; xx < xm; xx++)
						for(int yy = 0; yy < ym; yy++)
							sum += (unsigned char)src[(x * xm + xx + yy * pw) * nc + c];
					dst[x * nc + c] = (char)(sum >> shift);
				}
			}
		}

		texture->UnlockRect(level);
		texture->UnlockRect(level - 1);

		pw = w;
		ph = h;
		w = max(1, w / 2);
		h = max(1, h / 2);
	}
}

/*
 * Reads data from a render target texture. This is done by first copying the
 * data to a temporary offscreen surface using GetRenderTargetData function.
 * and then reading data from the offscreen surface.
 */
void Texture::readRenderTargetTexture(int level, int x, int y, int width, int height, void *data, int pitch, Scenic::Renderer::Format format)
{
	D3DLOCKED_RECT lockedRect;
	RECT rect;

	rect.left = x;
	rect.top = y;
	rect.right = x + width;
	rect.bottom = y + height;

	IDirect3DSurface9 *surface;
	IDirect3DSurface9 *tempSurface;

	texture->GetSurfaceLevel(0, &surface);
	direct3DDevice->CreateOffscreenPlainSurface(this->width, this->height, formats[this->textureFormat].d3dFormat,
		D3DPOOL_SYSTEMMEM, &tempSurface, NULL);
	direct3DDevice->GetRenderTargetData(surface, tempSurface);
	surface->Release();

	tempSurface->LockRect(&lockedRect, &rect, D3DLOCK_READONLY);

	char *dst = (char*)data;
	char *src = (char*)lockedRect.pBits;

	convert(src, lockedRect.Pitch, textureFormat,
		dst, pitch, format,
		width, height);

	tempSurface->UnlockRect();
	tempSurface->Release();
}


//
// CriticalRenderingSection
//


CriticalRenderingSection Scenic::Renderer::renderingLock;


void CriticalRenderingSection::onEnter()
{
}

void CriticalRenderingSection::onLeave()
{
}

//
// VertexDeclaration
//

VertexDeclaration::VertexDeclaration(ElementInfo *elements)
{
	numElements = 0;
	while(elements[numElements].size > 0)
		numElements++;
	this->elements = new ElementInfo[numElements];
	memcpy(this->elements, elements, numElements * sizeof(ElementInfo));

	handle = NULL;
}

VertexDeclaration::~VertexDeclaration()
{
	delete elements;
	if(handle)
		handle->Release();
}

int VertexDeclaration::getSize()
{
	int s = 0;
	int i;

	for(i = 0; i < numElements; i++)
		s += elements[i].getSize();

	return s;
}

//
// DXIndexBuffer
//

DXIndexBuffer::DXIndexBuffer(Device *device, int size)
{
	HRESULT res;

	this->size = size;
	res = device->getHandle()->CreateIndexBuffer(size, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 
		D3DFMT_INDEX32, D3DPOOL_DEFAULT, &buffer, NULL);

	if(res != D3D_OK)
	{
		fprintf(stderr, "CreateIndexBuffer failed! (result=%s)\n", getErrorName(res));
	}
}

DXIndexBuffer::~DXIndexBuffer()
{
	if(buffer)
		buffer->Release();
}

void *DXIndexBuffer::lock(int offset, int size)
{
	HRESULT res;
	void *data;

	res = buffer->Lock(offset, size, &data, D3DLOCK_DISCARD);
	if(res != D3D_OK)
	{
		fprintf(stderr, "IDirect3DIndexBuffer9::Lock failed! (result=%s)\n", getErrorName(res));
		return NULL;
	}
	return data;
}

void DXIndexBuffer::unlock()
{
	buffer->Unlock();
}

//
// DXVertexBuffer
//

DXVertexBuffer::DXVertexBuffer(Device *device, int size)
{
	HRESULT res;

	this->size = size;
	res = device->getHandle()->CreateVertexBuffer(size, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 
		0, D3DPOOL_DEFAULT, &buffer, NULL);

	if(res != D3D_OK)
	{
		fprintf(stderr, "CreateVertexBuffer failed! size = %d (result=%s)\n", size, getErrorName(res));
	}
}

DXVertexBuffer::~DXVertexBuffer()
{
	if(buffer)
		buffer->Release();
}

void *DXVertexBuffer::lock(int offset, int size)
{
	HRESULT res;
	void *data;

	res = buffer->Lock(offset, size, &data, D3DLOCK_DISCARD);
	if(res != D3D_OK)
	{
		fprintf(stderr, "IDirect3DVertexBuffer9::Lock failed! (result=%s)\n", getErrorName(res));
		return NULL;
	}
	return data;
}

void DXVertexBuffer::unlock()
{
	buffer->Unlock();
}

